tagged_union.hpp
namespace type_safe
{
template <typename T>
struct union_type;
template <typename ... Ts>
struct union_types;
template <typename ... Types>
class tagged_union;
template <typename ... Types, typename Func, typename ... Args>
void with(tagged_union<Types...>& u, Func&& f, Args&&... additional_args);
template <typename ... Types, typename Func, typename ... Args>
void with(const tagged_union<Types...>& u, Func&& f, Args&&... additional_args);
template <typename ... Types, typename Func, typename ... Args>
void with(tagged_union<Types...>&& u, Func&& f, Args&&... additional_args);
template <typename ... Types, typename Func, typename ... Args>
void with(const tagged_union<Types...>&& u, Func&& f, Args&&... additional_args);
template <typename ... Types>
void destroy(tagged_union<Types...>& u) noexcept;
template <typename ... Types>
void copy(tagged_union<Types...>& dest, const tagged_union<Types...>& org);
template <typename ... Types>
void move(tagged_union<Types...>& dest, tagged_union<Types...>&& org);
}
type_safe::union_type
[variant]template <typename T>
struct union_type
{
constexpr union_type();
};
Tag type so no explicit template instantiation of function parameters is required.
type_safe::union_types
[variant]template <typename ... Ts>
struct union_types
{
};
Very basic typelist.
type_safe::tagged_union
[variant]template <typename ... Types>
class tagged_union
{
public:
using types = union_types<typename std::decay<Types>::type...>;
class type_id;
static constexpr type_id invalid_type = type_id();
tagged_union() noexcept = default;
~tagged_union() noexcept = default;
tagged_union(const tagged_union&) = delete;
tagged_union& operator=(const tagged_union&) = delete;
template <typename T, typename ... Args>
void emplace(union_type<T>, Args&&... args);
template <typename T>
void destroy(union_type<T> type) noexcept;
const type_id& type() const noexcept;
bool has_value() const noexcept;
template <typename T>
T& value(union_type<T> type) & noexcept;
template <typename T>
const T& value(union_type<T> type) const & noexcept;
template <typename T>
T&& value(union_type<T> type) && noexcept;
template <typename T>
const T&& value(union_type<T> type) const && noexcept;
};
A tagged union.
It is much like a plain old C union
, but remembers which type it currently stores. It can either store one of the given types or no type at all.
Notes: Like the C union
it does not automatically destroy the currently stored type, and copy operations are deleted.
type_safe::tagged_union::type_id
class type_id
: public strong_typedef<class type_safe::tagged_union::type_id, std::size_t>,
public strong_typedef_op::equality_comparison<type_id>,
public strong_typedef_op::relational_comparison<type_id>
{
public:
template <typename T>
static constexpr bool is_valid();
constexpr type_id() noexcept;
template <typename T>
constexpr type_id(union_type<T>) noexcept;
operator bool() const noexcept;
};
The id of a type.
It is a ts::strong_typedef for std::size_t
and provides equality and relational comparison.
type_safe::tagged_union::type_id::is_valid
template <typename T>
static constexpr bool is_valid();
Returns: true
if T
is a valid type, false
otherwise.
type_safe::tagged_union::type_id::type_id
constexpr type_id() noexcept;
Effects: Initializes it to an invalid value.
Notes: The invalid value compares less than all valid values.
type_safe::tagged_union::type_id::type_id
template <typename T>
constexpr type_id(union_type<T>) noexcept;
Effects: Initializes it to the value of the type T
. If T
is not one of the types of the union types, it will be the same as the default constructor.
type_safe::tagged_union::type_id::operator bool
operator bool() const noexcept;
Returns: true
if the id is valid, false
otherwise.
type_safe::tagged_union::invalid_type
static constexpr type_id invalid_type = type_id();
A global invalid type id object.
type_safe::tagged_union::~tagged_union
~tagged_union() noexcept = default;
Notes: Does not destroy the currently stored type.
type_safe::tagged_union::emplace
template <typename T, typename ... Args>
void emplace(union_type<T>, Args&&... args);
Effects: Creates a new object of given type by perfectly forwarding args
.
Throws: Anything thrown by T
s constructor, in which case the union will stay empty.
Requires: The union must currently be empty. and T
must be a valid type and constructible from the arguments.
type_safe::tagged_union::destroy
template <typename T>
void destroy(union_type<T> type) noexcept;
Effects: Destroys the currently stored type by calling its destructor, and setting the union to the empty state.
Requires: The union must currently store an object of the given type.
type_safe::tagged_union::type
const type_id& type() const noexcept;
Returns: The type_id of the type currently stored, or invalid_type if there is none.
type_safe::tagged_union::has_value
bool has_value() const noexcept;
Returns: true
if there is a type stored, false
otherwise.
type_safe::tagged_union::value
(1) template <typename T>
T& value(union_type<T> type) & noexcept;
(2) template <typename T>
const T& value(union_type<T> type) const & noexcept;
(3) template <typename T>
T&& value(union_type<T> type) && noexcept;
(4) template <typename T>
const T&& value(union_type<T> type) const && noexcept;
Returns: A (const
) lvalue/rvalue reference to the currently stored type.
Requires: The union must currently store an object of the given type.
type_safe::with
[variant](1) template <typename ... Types, typename Func, typename ... Args>
void with(tagged_union<Types...>& u, Func&& f, Args&&... additional_args);
(2) template <typename ... Types, typename Func, typename ... Args>
void with(const tagged_union<Types...>& u, Func&& f, Args&&... additional_args);
(3) template <typename ... Types, typename Func, typename ... Args>
void with(tagged_union<Types...>&& u, Func&& f, Args&&... additional_args);
(4) template <typename ... Types, typename Func, typename ... Args>
void with(const tagged_union<Types...>&& u, Func&& f, Args&&... additional_args);
Effects: If the union is empty, does nothing. Otherwise let the union contain an object of type T
. If the functor is callable for the T
, calls its operator()
passing it the stored object. Else does nothing.
type_safe::destroy
[variant]template <typename ... Types>
void destroy(tagged_union<Types...>& u) noexcept;
Effects: Destroys the type currently stored in the ts::tagged_union, by calling u.destroy(union_type<T>{})
.
type_safe::copy
[variant](1) template <typename ... Types>
void copy(tagged_union<Types...>& dest, const tagged_union<Types...>& org);
(2) template <typename ... Types>
void move(tagged_union<Types...>& dest, tagged_union<Types...>&& org);
Effects: Copies the type currently stored in one ts::tagged_union to another. This is equivalent to calling dest.emplace(union_type<T>{}, org.value(union_type<T>{}))
(1)
dest.emplace(union_type<T>{}, std::move(org).value(union_type<T>{}))
(2), where T
is the type currently stored in the union.
Throws: Anything by the copy/move constructor in which case nothing has changed.
Requires: dest
must not store a type, and all types must be copyable/moveable.